package it.infodea.tapestrydea.services.jcr.nodes.wrappers;

import java.util.ArrayList;
import java.util.List;

import javax.jcr.AccessDeniedException;
import javax.jcr.InvalidItemStateException;
import javax.jcr.ItemExistsException;
import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.UnsupportedRepositoryOperationException;
import javax.jcr.ValueFormatException;
import javax.jcr.Workspace;
import javax.jcr.lock.LockException;
import javax.jcr.nodetype.ConstraintViolationException;
import javax.jcr.nodetype.NoSuchNodeTypeException;
import javax.jcr.query.Query;
import javax.jcr.query.QueryManager;
import javax.jcr.query.QueryResult;
import javax.jcr.version.VersionException;

/**
 * Questa classe fornisce i metodi basilari per la gestione di un nodo nel
 * repository. Va intesa come classe base da estendere per creare i propri node
 * wrappers
 * 
 * @author bobpuley
 * 
 */
public class JcrAbstractNode {

	protected static final String JCR_ROOT_PATH = "/";

	/**
	 * Il nodo jcr che si incapsula
	 */
	protected Node surroundedNode;

	/**
	 * La sessione jcr di riferimento
	 */
	protected Session jcrSession;

	/**
	 * Unico costruttore della classe, richiede un javax.jcr.Node, che è il nodo
	 * da incapsulare
	 * 
	 * @param surroundedNode
	 *            il nodo da incapsulare
	 * @throws RepositoryException
	 *             se si verificano errori nell'accesso alla sessione jcr
	 */
	public JcrAbstractNode(Node surroundedNode) throws RepositoryException {

		super();
		this.surroundedNode = surroundedNode;
		this.jcrSession = surroundedNode.getSession();

	}

	/**
	 * Resituisce il nome del nodo
	 * 
	 * @return il nome del nodo o null se il nodo incapsulato è null
	 * @throws RepositoryException
	 *             se si verifica un errore durante la lettura del repository
	 */
	public String getName() throws RepositoryException {

		String name = null;
		if (surroundedNode != null) {
			name = surroundedNode.getName();
		}
		return name;

	}

	/**
	 * Rinomina il nodo col newName
	 * 
	 * @param newName
	 *            il nuovo nome del nodo
	 * @throws RepositoryException
	 *             se si verifica un errore durante la lettura del repository
	 */
	public void rename(String newName) throws RepositoryException {

		// la funzione di rename, all'interno del repository avviene
		// effettuando un'operazione move dal path originale (comprensivo del
		// nome del nodo)
		// a quello finale (stesso parent path, ma differente nodeName)
		if (surroundedNode != null) {
			// prendo il path originale
			String path = getPath();
			// prendo il path del parent
			String parentPath = surroundedNode.getParent().getPath();
			// creo il nuovo path concatenando al parentPath il nuovo nome
			String newPath = parentPath + "/" + newName;
			// invoco la funzione move
			jcrSession.move(path, newPath);
			// salvo le modifiche
			jcrSession.save();
		}

	}

	/**
	 * Rimuove il nodo dal repository
	 * 
	 * @throws ConstraintViolationException
	 * @throws LockException
	 * @throws VersionException
	 * @throws RepositoryException
	 *             se si verifica un errore durante la lettura del repository
	 */
	public void remove() throws VersionException, LockException, ConstraintViolationException, RepositoryException {

		if (surroundedNode != null) {
			// rimuovo il nodo
			surroundedNode.remove();
			// salvo le modifiche
			jcrSession.save();
		}

	}

	/**
	 * Resituisce il path del nodo.
	 * 
	 * @return il path del nodo o null se il nodo incapsulato è null
	 * @throws RepositoryException
	 *             se si verifica un errore durante la lettura del repository
	 */
	public String getPath() throws RepositoryException {

		String path = null;
		// JcrDebugUtil.inspect(surroundedNode);
		if (surroundedNode != null) {
			path = surroundedNode.getPath();
		}
		return path;

	}

	/**
	 * restituisce lo UUID del nodo corrente
	 * 
	 * @return lo UUID del nodo corrente o null nel caso in cui il nodo
	 *         incapsulato sia nullo oppure il nodo sia la root del repository
	 * @throws RepositoryException
	 *             se si verifica un errore durante la lettura del repository
	 */
	public String getUUID() throws RepositoryException {

		String uuid = null;
		// JcrDebugUtil.inspect(surroundedNode);
		if (surroundedNode != null && !"/".equals(surroundedNode.getPath())) {
			uuid = surroundedNode.getUUID();
		}
		return uuid;

	}

	/**
	 * Verifica l'esistenza di un determinato nodo tra i propri childs
	 * 
	 * @param nodeName
	 *            il nome del nodo di cui si vuole verificare la presenza
	 * @return true se il nodo è presente
	 * @throws RepositoryException
	 *             se si verifica un errore durante la lettura del repository
	 */
	public boolean contains(String nodeName) throws RepositoryException {

		return surroundedNode.hasNode(nodeName);

	}

	/**
	 * Verifica se il nodo corrente sia o meno la root
	 * 
	 * @return tru se il path del nodo è /
	 * @throws RepositoryException
	 *             se si verifica un errore durante la lettura del repository
	 */
	public boolean isRoot() throws RepositoryException {

		return JCR_ROOT_PATH.equals(getPath());

	}

	/**
	 * estrae in modo esclusivo la risorsa garantendo che nessun altro utente la
	 * possa modificare fino al successivo checkin
	 * 
	 * @throws LockException
	 *             se il nodo è già estratto
	 * @throws UnsupportedRepositoryOperationException
	 * @throws RepositoryException
	 *             se si verifica un errore durante la lettura del repository
	 */
	public void checkOut() throws UnsupportedRepositoryOperationException, LockException, RepositoryException {

		if (surroundedNode != null) {
			surroundedNode.checkout();
			setCheckOwner();
			jcrSession.save();
		}

	}

	/**
	 * Verifica se il nodo è estratto
	 * 
	 * @return true se è estratto
	 * @throws RepositoryException
	 *             se si verifica un errore durante la lettura del repository
	 */
	public boolean isCheckedOut() throws RepositoryException {

		if (surroundedNode != null)
			return surroundedNode.isCheckedOut();

		return false;

	}

	/**
	 * Verifica se il file è estratto dall'utente corrente
	 * 
	 * @return true se il nodo è estratto dall'utente corrente
	 * @throws RepositoryException
	 *             se si verifica un errore durante la lettura del repository
	 */
	protected boolean isCheckedOutByMe() throws RepositoryException {

		if (surroundedNode != null && surroundedNode.isCheckedOut()) {
			String checkOwner = getCheckOwner();
			return checkOwner != null && jcrSession.getUserID().equals(checkOwner);
		}

		return false;

	}

	/**
	 * Estrare il nodo corrente
	 * 
	 * @throws VersionException
	 * @throws UnsupportedRepositoryOperationException
	 * @throws InvalidItemStateException
	 * @throws LockException
	 * @throws RepositoryException
	 */
	public void checkIn() throws VersionException, UnsupportedRepositoryOperationException, InvalidItemStateException, LockException,
			RepositoryException {

		if (isCheckedOut()) {
			// surroundedNode.setProperty("checkOwner", "-1");
			surroundedNode.checkin();
		}
	}

	/**
	 * Annulla il checkout e le eventuali modifiche apportate al nodo
	 * 
	 * @throws AccessDeniedException
	 * @throws ItemExistsException
	 * @throws ConstraintViolationException
	 * @throws InvalidItemStateException
	 * @throws VersionException
	 * @throws LockException
	 * @throws NoSuchNodeTypeException
	 * @throws RepositoryException
	 */
	public void undoCheckOut() throws AccessDeniedException, ItemExistsException, ConstraintViolationException, InvalidItemStateException,
			VersionException, LockException, NoSuchNodeTypeException, RepositoryException {

		// TODO: verificare il meccanismo per cancellare il checkout
		// probabilmente occore distinguere i nodi versionable dagli altri
		// per i nodi si dovrà, presumo, recuperare l'ultima versionee chiamare
		// il restore. Tuttavia anche il semplice refresh con il parametro
		// keepChanges a false potrebbe funzionare ed essere molto più sintetico
		// e elegante.
		if (surroundedNode != null) {
			surroundedNode.refresh(false);
			surroundedNode.save();
			surroundedNode.checkin();
		}

	}

	/**
	 * Committa le modifiche salvando e rilasciando la risorsa
	 * 
	 * @throws AccessDeniedException
	 * @throws ItemExistsException
	 * @throws ConstraintViolationException
	 * @throws InvalidItemStateException
	 * @throws VersionException
	 * @throws LockException
	 * @throws NoSuchNodeTypeException
	 * @throws RepositoryException
	 */
	protected void commitChanges() throws AccessDeniedException, ItemExistsException, ConstraintViolationException,
			InvalidItemStateException, VersionException, LockException, NoSuchNodeTypeException, RepositoryException {

		if (jcrSession != null && isCheckedOutByMe()) {
			jcrSession.save();
			checkIn();
		}

	}

	/**
	 * Esegue una query xpath sul workspace e restituisce i risultanti nodi in
	 * una List
	 * 
	 * @param query
	 * @return la lista dei nodi o una lista vuota
	 * @throws RepositoryException
	 */
	protected List<Node> query(String query) throws RepositoryException {

		ArrayList<Node> results = new ArrayList<Node>();
		Workspace ws = jcrSession.getWorkspace();
		QueryManager qm = ws.getQueryManager();

		// Specify a query using the XPATH query language
		Query q = qm.createQuery(query, Query.XPATH);
		QueryResult res = q.execute();

		// Obtain a node iterator
		NodeIterator it = res.getNodes();

		while (it.hasNext()) {
			results.add(it.nextNode());
		}
		return results;

	}

	/**
	 * Consente di accedere direttamente al nodo incapsulato è pensato come
	 * metodo ad uso interno, ma in casi estremi può tornare comodo.
	 * 
	 * @return il node incapsulato
	 */
	public Node getSurroundedNode() {

		return surroundedNode;

	}

	// TODO [Marco]: verificare il meccanismo
	private void setCheckOwner() throws ValueFormatException, VersionException, LockException, ConstraintViolationException,
			RepositoryException {
		// surroundedNode.setProperty("checkOwner", jcrSession.getUserID());
	}

	// TODO [Marco]: verificare il meccanismo
	private String getCheckOwner() throws ValueFormatException, VersionException, LockException, ConstraintViolationException,
			RepositoryException {

		String checkOwner = null;
		if (surroundedNode.hasProperty("checkOwner")) {
			checkOwner = surroundedNode.getProperty("checkOwner").getString();
		}
		return checkOwner;

	}

}